/*****************************************************************************************
   
   Copyright (c) Microsoft Corporation. All rights reserved.

   Use of this code sample is subject to the terms of the Microsoft
   Permissive License, a copy of which should always be distributed with
   this file.  You can also access a copy of this license agreement at:
   http://www.microsoft.com/resources/sharedsource/licensingbasics/permissivelicense.mspx

 ****************************************************************************************/

using System;
using System.Collections;

namespace GrooveWebServicesV12Helpers
{
	/// <summary>
	/// Manager for web services event subscriptions.
	/// Applications will usually not use this class directly; instead use the event delegates in each data class.
	/// </summary>
	public class EventManager
	{
		//  ---------------------------------------------------------
		//  BIG HONKING NOTES:
		//  1) You must always start event URIs with a prefix of
		//     "dpp://localhost/" in order to receive events
		//  2) Event URIs represent queue names, so ideally you only
		//     need one per process

		public static string s_EventURI = @"dpp://localhost/GrooveWebServicesHelpers/Event_" + new System.Random().Next().ToString();

		//  ---------------------------------------------------------

		public const int DEFAULT_TIME_TO_LIVE_IN_HOURS = 8;
        public const int DEFAULT_SLEEP_DURATION_IN_MILLISECS = 2500;

		private static System.Threading.Thread s_EventManagerThread; 

		private static System.Collections.Hashtable s_EventHashtable = new System.Collections.Hashtable();
		private static System.Collections.Hashtable s_EventManagerHashtable = new System.Collections.Hashtable();
		private static System.Collections.Hashtable s_SubscriptionIDHashtable = new System.Collections.Hashtable();

		private static System.Collections.Stack s_SubscriptionIDIgnoreStack = new System.Collections.Stack();

		private static bool m_EventsEnabled = true;

        private static int s_SleepDuration = DEFAULT_SLEEP_DURATION_IN_MILLISECS;  //  Default is 2.5 seconds
        private static int s_MaxEventsPerRead = -1; //  Default is all events

		private const char EVENT_INFO_DELIMITER = '^';

		private GrooveWebServicesV12Helpers.Context m_Context;
		private GrooveWebServicesV12Helpers.Event m_Event;
		private GrooveWebServicesV12Helpers.Subscription m_Subscription;

		private System.Collections.ArrayList m_EventCallbackArrayList = new System.Collections.ArrayList();

		private string m_IdentityURL;
		private string m_EventClass;
		private string m_Query;
		private string m_SourceURI;
		private string m_CallbackURI;
		private string m_Key;

		private int m_UsageCount;
		private int m_TimeToLive;
		private DateTime m_SubscriptionLastUpdated;

		public EventManager(GrooveWebServicesV12Helpers.Context i_Context, string i_IdentityURL, string i_EventClass, string i_SourceURI, string i_CallbackURI)
			: this(i_Context, i_IdentityURL, i_EventClass, null, i_SourceURI, i_CallbackURI, null, GrooveWebServicesV12Helpers.EventManager.DEFAULT_TIME_TO_LIVE_IN_HOURS)
		{
		}

		public EventManager(GrooveWebServicesV12Helpers.Context i_Context, string i_IdentityURL, string i_EventClass, string i_SourceURI, string i_CallbackURI, string i_SubscriptionID)
			: this(i_Context, i_IdentityURL, i_EventClass, null, i_SourceURI, i_CallbackURI, i_SubscriptionID, GrooveWebServicesV12Helpers.EventManager.DEFAULT_TIME_TO_LIVE_IN_HOURS)
		{
		}

		public EventManager(GrooveWebServicesV12Helpers.Context i_Context, string i_IdentityURL, string i_EventClass, string i_SourceURI, string i_CallbackURI, int i_TimeToLive)
			: this(i_Context, i_IdentityURL, i_EventClass, null, i_SourceURI, i_CallbackURI, null, i_TimeToLive)
		{
		}

		public EventManager(GrooveWebServicesV12Helpers.Context i_Context, string i_IdentityURL, string i_EventClass, string i_SourceURI, string i_CallbackURI, string i_SubscriptionID, int i_TimeToLive)
			: this(i_Context, i_IdentityURL, i_EventClass, null, i_SourceURI, i_CallbackURI, i_SubscriptionID, i_TimeToLive)
		{
		}

		public EventManager(GrooveWebServicesV12Helpers.Context i_Context, string i_IdentityURL, string i_EventClass, string i_Query, string i_SourceURI, string i_CallbackURI, string i_SubscriptionID, int i_TimeToLive)
		{
			m_Context = i_Context;

			m_IdentityURL = i_IdentityURL;
			m_EventClass = i_EventClass;
			m_Query = i_Query;
			m_SourceURI = i_SourceURI;
			m_CallbackURI = i_CallbackURI;

			m_TimeToLive = i_TimeToLive;
            ResetSubscriptionLifetime();

			m_Key = m_IdentityURL + m_EventClass + m_CallbackURI;
			if (!System.String.IsNullOrEmpty(i_SubscriptionID))
				m_Key += i_SubscriptionID;

			//  Create event
			m_Event = new GrooveWebServicesV12Helpers.Event
				(
				i_Context,
				i_IdentityURL,
				i_CallbackURI
				);

			//  Create subscription
			m_Subscription = new GrooveWebServicesV12Helpers.Subscription
				(
				i_Context,
				i_IdentityURL,
				i_EventClass,
				i_Query,
				i_SourceURI,
				i_CallbackURI,
				i_SubscriptionID,
				i_TimeToLive,
				true
				);
		}

		#region EventManager Properties And Methods

		public static bool EventsEnabled
		{
			set
			{
				lock(s_EventHashtable)
				{
					m_EventsEnabled = value;
				}
			}

            get
            {
                return m_EventsEnabled;
            }
		}
		
		public GrooveWebServicesV12Helpers.Context Context
		{
			get
			{
				return m_Context;
			}
		}

		public string IdentityURL
		{
			get
			{
				return m_IdentityURL;
			}
		}

		public string EventClass
		{
			get
			{
				return m_EventClass;
			}
		}

		public string Query
		{
			get
			{
				return m_Query;
			}
		}

		public System.Collections.ArrayList EventCallbackArrayList
		{
			get
			{
				return m_EventCallbackArrayList;
			}
		}

		public string SourceURI
		{
			get
			{
				return m_SourceURI;
			}
		}

		public string CallbackURI
		{
			get
			{
				return m_CallbackURI;
			}
		}

		public GrooveWebServicesV12Helpers.Event Event
		{
			get
			{
				return m_Event;
			}
		}

		public GrooveWebServicesV12Helpers.Subscription Subscription
		{
			get
			{
				return m_Subscription;
			}
		}

		public string Key
		{
			get
			{
				return m_Key;
			}
		}

		public int UsageCount
		{
			get
			{
				return m_UsageCount;
			}
		}

		public int TimeToLive
		{
			get
			{
				return m_TimeToLive;
			}
		}

		public int LifetimeInHours
		{
			get
			{
                TimeSpan subscriptionLifetime = DateTime.Now.Subtract(m_SubscriptionLastUpdated);
                return (int) (subscriptionLifetime.TotalHours + 0.5);
			}
		}

        private void ResetSubscriptionLifetime()
        {
            m_SubscriptionLastUpdated = DateTime.Now;
        }

		public void ProcessRawEvent(GrooveEventsWebService.Event i_RawEvent)
		{
			foreach (GrooveWebServicesV12Helpers.Shared.IGrooveWebServicesEventCallback IGrooveWebServicesEventCallback in m_EventCallbackArrayList)
			{
				try
				{
					if (GrooveWebServicesV12Helpers.Context.TraceEnabled)
					{
                        string Message = System.String.Format
                            (
						    "EventManager.ProcessRawEvent() now calling callback passing raw event with SubscriptionID {0}.", 
                            i_RawEvent.SubscriptionID
                            );

						System.Diagnostics.Debug.WriteLine(Message);
					}
					IGrooveWebServicesEventCallback.ProcessEvent(i_RawEvent);
				}
				catch (System.Exception Exception)
				{
					System.Diagnostics.Debug.WriteLine("Exception during ProcessRawEvent: " + Exception.Message);
					System.Diagnostics.Debug.WriteLine(Exception.StackTrace);
				}
			}
		}

		public void StartSubscription()
		{
			m_Subscription.Start();
		}

		public void UpdateSubscription()
		{
            int retries = 0;
            string exceptionMsg = null;
            do {
                try
                {
                    m_Subscription.Update();
                    break;
                }
                catch (System.Web.Services.Protocols.SoapException soapEx)
                {
                    exceptionMsg = soapEx.Message;
                }

                if (exceptionMsg == null)
                {
                    break;
                }

                System.Diagnostics.Debug.WriteLine("UpdateSubscription: " + exceptionMsg);
                if (exceptionMsg == "Groove request key does not match")
                {
                    // this code simply replaces the Context object with new Context
                    // object that has a request key that matches Groove's
                    // this is no longer necessary, because the code always reads the 
                    // request key from the registry, and so this isn't likely to happen
                    // but I leave it in case someone doesn't like reading the registry alot.
                    Context ctx = Context.NewContext(m_Subscription.Context);
                    m_Subscription = new Subscription(m_Subscription, ctx);
                }
                else if (exceptionMsg == NoSuchElementMsg())
                {
                    // you just tried to update a subscription that has expired. 
                    // The client (the ultimate client) needs to re-subscribe
                    m_Subscription.ErrorMsg = exceptionMsg;
                    break;
                }
                else
                {
                    System.Diagnostics.Debug.WriteLine("Error in UpdateSubscription: " + exceptionMsg);
                    break;
                }
            } while (retries++ < 2);

			foreach (GrooveWebServicesV12Helpers.Shared.IGrooveWebServicesEventCallback IGrooveWebServicesEventCallback in m_EventCallbackArrayList)
			{
				if (IGrooveWebServicesEventCallback is GrooveWebServicesV12Helpers.Shared.IGrooveWebServicesAdvancedEventCallback)
				{
					GrooveWebServicesV12Helpers.Shared.IGrooveWebServicesAdvancedEventCallback IGrooveWebServicesAdvancedEventCallback = (GrooveWebServicesV12Helpers.Shared.IGrooveWebServicesAdvancedEventCallback)IGrooveWebServicesEventCallback;
			
					try
					{
						IGrooveWebServicesAdvancedEventCallback.OnSubscriptionUpdated(m_Subscription);
					}
					catch (System.Exception Exception)
					{
						System.Diagnostics.Debug.WriteLine("Exception during advanced event callback(2): " + Exception.Message);
					}
				}
			}
		}

        private string NoSuchElementMsg()
        {
            //  "Could not find element with ID=w8h7aj7yz68s794b6rdh5wabejdhpce38kkx4ai within the document"
            string noSuchElementMsg 
                = String.Format(
                    "Could not find element with ID={0} within the document", 
                    m_Subscription.SubscriptionID);

            return noSuchElementMsg;
        }

		public void StopSubscription()
		{
            try
            {
                m_Subscription.Stop();
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(
                    String.Format(
                        "StopSubscription: Exception: ID={0}\r\n\t Message={1}\r\n\t Stack={2}", 
                        m_Subscription.SubscriptionID,
                        ex.Message, 
                        ex.StackTrace));
            }
		}

		public void SignalEventsProcessingCompleted(long i_SequenceNumber)
		{
			foreach (GrooveWebServicesV12Helpers.Shared.IGrooveWebServicesEventCallback IGrooveWebServicesEventCallback in m_EventCallbackArrayList)
			{
				if (IGrooveWebServicesEventCallback is GrooveWebServicesV12Helpers.Shared.IGrooveWebServicesAdvancedEventCallback)
				{
					GrooveWebServicesV12Helpers.Shared.IGrooveWebServicesAdvancedEventCallback IGrooveWebServicesAdvancedEventCallback = (GrooveWebServicesV12Helpers.Shared.IGrooveWebServicesAdvancedEventCallback)IGrooveWebServicesEventCallback;
			
					try
					{
						IGrooveWebServicesAdvancedEventCallback.OnEventProcessingCompleted
							(
							m_Subscription,
							i_SequenceNumber
							);
					}
					catch (System.Exception Exception)
					{
						System.Diagnostics.Debug.WriteLine("Exception during advanced event callback(4): " + Exception.Message);
					}
				}
			}
		}

		public void SignalEventsDeleted(long i_SequenceNumber)
		{
			foreach (GrooveWebServicesV12Helpers.Shared.IGrooveWebServicesEventCallback IGrooveWebServicesEventCallback in m_EventCallbackArrayList)
			{
				if (IGrooveWebServicesEventCallback is GrooveWebServicesV12Helpers.Shared.IGrooveWebServicesAdvancedEventCallback)
				{
					GrooveWebServicesV12Helpers.Shared.IGrooveWebServicesAdvancedEventCallback IGrooveWebServicesAdvancedEventCallback = (GrooveWebServicesV12Helpers.Shared.IGrooveWebServicesAdvancedEventCallback)IGrooveWebServicesEventCallback;
			
					try
					{
						IGrooveWebServicesAdvancedEventCallback.OnEventsDeleted
							(
							m_Subscription,
							i_SequenceNumber
							);
					}
					catch (System.Exception Exception)
					{
						System.Diagnostics.Debug.WriteLine("Exception during advanced event callback(5): " + Exception.Message);
					}
				}
			}
		}

		public void AddEventCallback(GrooveWebServicesV12Helpers.Shared.IGrooveWebServicesEventCallback i_IGrooveWebServicesEventCallback)
		{
			this.AddEventCallback
				(
				m_TimeToLive,
				i_IGrooveWebServicesEventCallback
				);
		}

		public void AddEventCallback(int i_TimeToLive, GrooveWebServicesV12Helpers.Shared.IGrooveWebServicesEventCallback i_IGrooveWebServicesEventCallback)
		{
			//  If time to live is less than current value, we need to reassign value because we always
			//  want the lowest value for updating/renewing subscriptions
			if (i_TimeToLive < m_TimeToLive)
				m_TimeToLive = i_TimeToLive;

			++m_UsageCount;
			m_EventCallbackArrayList.Add(i_IGrooveWebServicesEventCallback);
		}

		public void RemoveEventCallback(GrooveWebServicesV12Helpers.Shared.IGrooveWebServicesEventCallback i_IGrooveWebServicesEventCallback)
		{
			foreach (GrooveWebServicesV12Helpers.Shared.IGrooveWebServicesEventCallback IGrooveWebServicesEventCallback in m_EventCallbackArrayList)
			{
				if (IGrooveWebServicesEventCallback == i_IGrooveWebServicesEventCallback)
				{
					m_EventCallbackArrayList.Remove(IGrooveWebServicesEventCallback);
					--m_UsageCount;

					return;
				}
			}

			throw new System.ArgumentException("Unable to find matching event callback!");
		}

		public static string AddEventListener(GrooveWebServicesV12Helpers.Context i_Context, string i_IdentityURL, string i_EventClass, string i_SourceURI, string i_CallbackURI, GrooveWebServicesV12Helpers.Shared.IGrooveWebServicesEventCallback i_IGrooveWebServicesEventCallback)
		{
			return GrooveWebServicesV12Helpers.EventManager.AddEventListener
				(
				i_Context,
				i_IdentityURL,
				i_EventClass,
				null,			// Query
				i_SourceURI,
				i_CallbackURI,
				null,			// Subscription ID
				GrooveWebServicesV12Helpers.Subscription.DEFAULT_TIME_TO_LIVE_IN_HOURS,
				i_IGrooveWebServicesEventCallback
				);
		}

		public static string AddEventListener(GrooveWebServicesV12Helpers.Context i_Context, string i_IdentityURL, string i_EventClass, string i_SourceURI, string i_CallbackURI, int i_TimeToLive, GrooveWebServicesV12Helpers.Shared.IGrooveWebServicesEventCallback i_IGrooveWebServicesEventCallback)
		{
			return GrooveWebServicesV12Helpers.EventManager.AddEventListener
				(
				i_Context,
				i_IdentityURL,
				i_EventClass,
				null,			// Query
				i_SourceURI,
				i_CallbackURI,
				null,			// Subscription ID
				i_TimeToLive,
				i_IGrooveWebServicesEventCallback
				);
		}

		public static void AddEventListener(GrooveWebServicesV12Helpers.Context i_Context, string i_IdentityURL, string i_EventClass, string i_SourceURI, string i_CallbackURI, string i_SubscriptionID, GrooveWebServicesV12Helpers.Shared.IGrooveWebServicesEventCallback i_IGrooveWebServicesEventCallback)
		{
			GrooveWebServicesV12Helpers.EventManager.AddEventListener
				(
				i_Context,
				i_IdentityURL,
				i_EventClass,
				null,			// Query
				i_SourceURI,
				i_CallbackURI,
				i_SubscriptionID,
				GrooveWebServicesV12Helpers.Subscription.DEFAULT_TIME_TO_LIVE_IN_HOURS,
				i_IGrooveWebServicesEventCallback
				);
		}

		public static void AddEventListener(GrooveWebServicesV12Helpers.Context i_Context, string i_IdentityURL, string i_EventClass, string i_SourceURI, string i_CallbackURI, string i_SubscriptionID, int i_TimeToLive, GrooveWebServicesV12Helpers.Shared.IGrooveWebServicesEventCallback i_IGrooveWebServicesEventCallback)
		{
			GrooveWebServicesV12Helpers.EventManager.AddEventListener
				(
				i_Context, 
				i_IdentityURL, 
				i_EventClass,
				null,			// Query
				i_SourceURI, 
				i_CallbackURI, 
				i_SubscriptionID, 
				i_TimeToLive, 
				i_IGrooveWebServicesEventCallback
				);
		}

		public static string AddAggregatedEventListener(GrooveWebServicesV12Helpers.Context i_Context, string i_IdentityURL, string i_EventClass, string i_SourceURI, string i_CallbackURI, GrooveWebServicesV12Helpers.Shared.IGrooveWebServicesEventCallback i_IGrooveWebServicesEventCallback)
		{
			return GrooveWebServicesV12Helpers.EventManager.AddEventListener
				(
				i_Context,
				i_IdentityURL,
				i_EventClass,
				"",				// Query
				i_SourceURI,
				i_CallbackURI,
				null,			// Subscription ID
				DEFAULT_TIME_TO_LIVE_IN_HOURS,
				i_IGrooveWebServicesEventCallback
				);
		}

		public static string AddAggregatedEventListener(GrooveWebServicesV12Helpers.Context i_Context, string i_IdentityURL, string i_EventClass, string i_Query, string i_SourceURI, string i_CallbackURI, GrooveWebServicesV12Helpers.Shared.IGrooveWebServicesEventCallback i_IGrooveWebServicesEventCallback)
		{
			return GrooveWebServicesV12Helpers.EventManager.AddEventListener
				(
				i_Context,
				i_IdentityURL,
				i_EventClass,
				i_Query,
				i_SourceURI,
				i_CallbackURI,
				null,			// Subscription ID
				DEFAULT_TIME_TO_LIVE_IN_HOURS,
				i_IGrooveWebServicesEventCallback
				);
		}

		public static void AddAggregatedEventListener(GrooveWebServicesV12Helpers.Context i_Context, string i_IdentityURL, string i_EventClass, string i_Query, string i_SourceURI, string i_CallbackURI, string i_SubscriptionID, GrooveWebServicesV12Helpers.Shared.IGrooveWebServicesEventCallback i_IGrooveWebServicesEventCallback)
		{
			GrooveWebServicesV12Helpers.EventManager.AddEventListener
				(
				i_Context,
				i_IdentityURL,
				i_EventClass,
				i_Query,
				i_SourceURI,
				i_CallbackURI,
				i_SubscriptionID,
				DEFAULT_TIME_TO_LIVE_IN_HOURS,
				i_IGrooveWebServicesEventCallback
				);
		}

		public static string AddAggregatedEventListener(GrooveWebServicesV12Helpers.Context i_Context, string i_IdentityURL, string i_EventClass, string i_Query, string i_SourceURI, string i_CallbackURI, int i_TimeToLive, GrooveWebServicesV12Helpers.Shared.IGrooveWebServicesEventCallback i_IGrooveWebServicesEventCallback)
		{
			return GrooveWebServicesV12Helpers.EventManager.AddEventListener
				(
				i_Context,
				i_IdentityURL,
				i_EventClass,
				i_Query,
				i_SourceURI,
				i_CallbackURI,
				null,			// Subscription ID
				i_TimeToLive,
				i_IGrooveWebServicesEventCallback
				);
		}

        public static string AddAggregatedEventListener(GrooveWebServicesV12Helpers.Context i_Context, string i_IdentityURL, string i_EventClass, string i_Query, string i_SourceURI, string i_CallbackURI, string i_SubscriptionID, int i_TimeToLive, GrooveWebServicesV12Helpers.Shared.IGrooveWebServicesEventCallback i_IGrooveWebServicesEventCallback)
		{
			return GrooveWebServicesV12Helpers.EventManager.AddEventListener
				(
				i_Context,
				i_IdentityURL,
				i_EventClass,
				i_Query,
				i_SourceURI,
				i_CallbackURI,
				i_SubscriptionID,
				i_TimeToLive,
				i_IGrooveWebServicesEventCallback
				);
		}

		internal static string AddEventListener(GrooveWebServicesV12Helpers.Context i_Context, string i_IdentityURL, string i_EventClass, string i_Query, string i_SourceURI, string i_CallbackURI, string i_SubscriptionID, int i_TimeToLive, GrooveWebServicesV12Helpers.Shared.IGrooveWebServicesEventCallback i_IGrooveWebServicesEventCallback)
		{
			string EventKey = i_IdentityURL + i_CallbackURI;
			string EventManagerKey = EventKey + i_EventClass;
			string SubscriptionID = i_SubscriptionID;

			if (!System.String.IsNullOrEmpty(SubscriptionID))
				EventManagerKey += SubscriptionID;

			GrooveWebServicesV12Helpers.EventManager EventManager = null;

			lock (s_EventHashtable)
			{				
				if (s_EventHashtable.Contains(EventKey))
				{
					//  Get existing of event manager
					EventManager = (GrooveWebServicesV12Helpers.EventManager)s_EventManagerHashtable[EventManagerKey];
				}
				else
				{
					//  If callback uri hash table is empty, start event thread	
					if (s_EventHashtable.Count == 0)
					{
						s_EventManagerThread = new System.Threading.Thread(new System.Threading.ThreadStart(EventManagerThreadProc));
						s_EventManagerThread.Start();
					}

					//  Create and cache event
					GrooveWebServicesV12Helpers.Event Event = new GrooveWebServicesV12Helpers.Event
						(
						i_Context,
						i_IdentityURL,
						i_CallbackURI
						);

					s_EventHashtable[EventKey] = Event;
				}

				//  Create instance of event manager if necessary
				if (EventManager == null)
				{
					EventManager = new GrooveWebServicesV12Helpers.EventManager
						(
						i_Context,
						i_IdentityURL,
						i_EventClass,
						i_Query,
						i_SourceURI,
						i_CallbackURI,
						i_SubscriptionID,
						i_TimeToLive
						);

					//  Fix up key to include subscription id
					if (System.String.IsNullOrEmpty(SubscriptionID))
					{
						SubscriptionID = EventManager.Subscription.SubscriptionID;
						EventManagerKey += SubscriptionID;
					}

                    string Message = System.String.Format
                        (
						"AddEventListener(): Adding {0} to s_EventManagerHashtable", 
						EventManagerKey
                        );

					System.Diagnostics.Debug.WriteLine(Message);

					//  Cache instance of event manager
					s_EventManagerHashtable[EventManagerKey] = EventManager;

					//  Cache delimited event keys
					s_SubscriptionIDHashtable[SubscriptionID] = EventKey + EVENT_INFO_DELIMITER + EventManagerKey;
				}

				EventManager.AddEventCallback
					(
					i_TimeToLive,
					i_IGrooveWebServicesEventCallback
					);

				return SubscriptionID;
			}
		}

		public static void RemoveEventListener(string i_SubscriptionID, GrooveWebServicesV12Helpers.Shared.IGrooveWebServicesEventCallback i_IGrooveWebServicesEventCallback)
		{
			GrooveWebServicesV12Helpers.EventManager.RemoveEventListener
				(
				i_SubscriptionID,
				i_IGrooveWebServicesEventCallback,
				false
				);
		}

		public static void RemoveEventListener(string i_SubscriptionID, GrooveWebServicesV12Helpers.Shared.IGrooveWebServicesEventCallback i_IGrooveWebServicesEventCallback, bool i_PersistSubscription)
		{
			GrooveWebServicesV12Helpers.EventManager EventManager = null;

			lock (s_EventHashtable)
			{
				if (!s_SubscriptionIDHashtable.Contains(i_SubscriptionID))
					throw new System.ArgumentException("One or more arguments are invalid(2)!");

				string DelimitedEventInfo = (string)s_SubscriptionIDHashtable[i_SubscriptionID];
				string[] EventInfo = DelimitedEventInfo.Split(EVENT_INFO_DELIMITER);

				string EventKey = EventInfo[0];
				string EventManagerKey = EventInfo[1];

				s_SubscriptionIDHashtable.Remove(i_SubscriptionID);

				//  Get existing instance of event manager
				EventManager = (GrooveWebServicesV12Helpers.EventManager)s_EventManagerHashtable[EventManagerKey];
				if (EventManager == null)
					throw new System.ArgumentException("One or more arguments are invalid(3)!");
	
				EventManager.RemoveEventCallback(i_IGrooveWebServicesEventCallback);

				//  Decrement count and exit loop
				if (EventManager.UsageCount == 0)
				{
					//  Stop subscription if necessary
					if (!i_PersistSubscription)
						EventManager.StopSubscription();

					//  Remove entry from event manager hashtable
					s_EventManagerHashtable.Remove(EventManagerKey);

                    bool NeedToRemoveEvent = true;

                    //  Iterate keys of event manager hashtable
                    foreach (string EventManagerHashtableKey in s_EventManagerHashtable.Keys)
                    {
                        //  See if EventKey is first part of EventManagerKey
                        if (EventManagerHashtableKey.IndexOf(EventKey) == 0)
                        {
                            //  If we find EventKey in EventManagerKey, there
                            //  are other event manager instances that need
                            //  the event to stick around
                            NeedToRemoveEvent = false;
                            break;
                        }
                    }

					if (NeedToRemoveEvent)
					{
						//  No other event manager instances exist, so remove entry from event hashtable
						s_EventHashtable.Remove(EventKey);

						if (s_EventHashtable.Count == 0)
						{
							//  Event hashtable is empty, so stop event thread	
							s_EventManagerThread.Abort();
							s_EventManagerThread = null;
						}
					}
				}
			}
		}

        public static void UpdateEventListener(string i_IdentityURL, string i_CallbackURI, string i_EventClass, string i_SubscriptionID)
        {
            lock (s_EventHashtable)
            {
                string EventKey = i_IdentityURL + i_CallbackURI;
                string EventManagerKey = EventKey + i_EventClass + i_SubscriptionID;

                GrooveWebServicesV12Helpers.EventManager EventManager = (GrooveWebServicesV12Helpers.EventManager)s_EventManagerHashtable[EventManagerKey];
                if (EventManager == null)
                    throw new System.Exception("EventManager instance not found(6)!");

                EventManager.UpdateSubscription();
                EventManager.ResetSubscriptionLifetime();
            }
        }

		#endregion

		#region EventManagerThreadProc

		private static void EventManagerThreadProc()
        {
            try
            {
                Microsoft.Win32.RegistryKey HKLM_GrooveWebServicesHelpersRegKey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(GrooveWebServicesV12Helpers.Shared.GROOVE_WEB_SERVICES_HELPERS_REG_KEY);
                if (HKLM_GrooveWebServicesHelpersRegKey != null)
                {
                    s_SleepDuration = (int)HKLM_GrooveWebServicesHelpersRegKey.GetValue("EventPollingInterval", DEFAULT_SLEEP_DURATION_IN_MILLISECS);
                    s_MaxEventsPerRead = (int)HKLM_GrooveWebServicesHelpersRegKey.GetValue("MaxEventsPerRead", -1);
                }
            }
            catch (System.Exception)
            {
                s_SleepDuration = DEFAULT_SLEEP_DURATION_IN_MILLISECS;
                s_MaxEventsPerRead = -1;
            }
            finally
            {
                if (GrooveWebServicesV12Helpers.Context.TraceEnabled)
                {
                    System.Diagnostics.Debug.WriteLine("Setting SleepDuration for event polling to: " + s_SleepDuration.ToString());
                    System.Diagnostics.Debug.WriteLine("Setting MaxEventsPerRead for event polling to: " + s_MaxEventsPerRead.ToString());
                }
            }

            do
            {
                do
                {
                    try
                    {
                        lock (s_EventHashtable)
                        {
                            if (m_EventsEnabled)
                            {
                                //  Iterate through event hash table
                                foreach (string EventKey in s_EventHashtable.Keys)
                                {
                                    GrooveWebServicesV12Helpers.Event Event = (GrooveWebServicesV12Helpers.Event)s_EventHashtable[EventKey];

                                    //long Sequence = -1;
                                    long Sequence = 0;  // per doc to read all events in queue ...rgn

                                    //  See if event throttling set
                                    if (s_MaxEventsPerRead != -1)
                                    {
                                        //  Try to read the the specified number of events 
                                        GrooveWebServicesV12Helpers.GrooveEventsWebService.EventQueueSummary EventQueueSummary = Event.ReadSummary();
                                        if ((EventQueueSummary.Count > 0) && (EventQueueSummary.Count > s_MaxEventsPerRead))
                                        {
                                            //  --------------------------------------------------------------------
                                            //  BIG HONKING NOTE:  Some Groove tools increment the sequence by 2
                                            //                     instead of by 1.  The calculation below will not 
                                            //                     be broken in those cases, the behavior will be
                                            //                     half the number of events per cycle, which means
                                            //                     more cycles will occur

                                            Sequence = EventQueueSummary.Sequence - EventQueueSummary.Count + s_MaxEventsPerRead;

                                            //  --------------------------------------------------------------------
                                        }
                                    }

                                    //  Ask service to provide the list of events, but not to delete them
                                    GrooveEventsWebService.ExtendedEventData ExtendedEventData = Event.ReadExtended
                                        (
                                        Sequence,
                                        true,       //  Include sources
                                        false       //  Delete events
                                        );

                                    //  If no events, just continue loop
                                    if (ExtendedEventData.EventQueueSummary.Count == 0)
                                    {
                                        if (GrooveWebServicesV12Helpers.Context.TraceEnabled)
                                        {
                                            System.Diagnostics.Debug.WriteLine("EventManagerThreadProc(): Event.ReadExtended returned event count=0.");
                                        }
                                        continue;
                                    }

                                    System.Collections.Hashtable EventManagerHashtable = new System.Collections.Hashtable();

                                    foreach (GrooveEventsWebService.Event RawEvent in ExtendedEventData.EventList)
                                    {
                                        string rawEventSubscriptionID = RawEvent.SubscriptionID;

                                        if (GrooveWebServicesV12Helpers.Context.TraceEnabled)
                                        {
                                            System.Diagnostics.Debug.WriteLine
                                                (
                                                "GrooveEventsWebService Raw Event:" +
                                                " Class='" + RawEvent.EventClass + "'" +
                                                " Type='" + RawEvent.EventType + "'" +
                                                " Source='" + RawEvent.EventSource + "'" +
                                                " SubscriptionID='" + rawEventSubscriptionID + "'" +
                                                " Data='" + RawEvent.EventData + "'"
                                                );
                                        }

                                        //  If SubscriptionID found in stack of items to ignore,
                                        //  continue to loop to the next raw event in the EventList
                                        if (s_SubscriptionIDIgnoreStack.Contains(rawEventSubscriptionID))
                                        {
                                            if (GrooveWebServicesV12Helpers.Context.TraceEnabled)
                                            {
                                                string Message = System.String.Format
                                                    (
                                                    "EventManagerThreadProc(): SubscriptionID='{0}' found in stack of items to ignore. Ignoring event.",
                                                    rawEventSubscriptionID
                                                    );

                                                System.Diagnostics.Debug.WriteLine(Message);
                                            }
                                            continue;
                                        }

                                        string EventManagerKey = Event.IdentityURL + Event.CallbackURI + RawEvent.EventClass + rawEventSubscriptionID;

                                        //  Get corresponding event manager instance
                                        GrooveWebServicesV12Helpers.EventManager EventManager = (GrooveWebServicesV12Helpers.EventManager)s_EventManagerHashtable[EventManagerKey];
                                        if (EventManager == null)
                                        {
                                            //  If we get here then all events for the associated CallBackURI 
                                            //  should be ignored
                                            if (GrooveWebServicesV12Helpers.Context.TraceEnabled)
                                            {
                                                string Message = System.String.Format
                                                    (
                                                    "EventManagerThreadProc(): cannot find instance of EventManager with key '{0}'.",
                                                    EventManagerKey
                                                    );

                                                System.Diagnostics.Debug.WriteLine(Message);

                                                Message = "EventManagerThreadProc(): in s_EventManagerHashtable";
                                                System.Collections.IDictionaryEnumerator IDictionaryEnumerator = s_EventManagerHashtable.GetEnumerator();
                                                while (IDictionaryEnumerator.MoveNext())
                                                {
                                                    Message += ", " + IDictionaryEnumerator.Key;
                                                }
                                                System.Diagnostics.Debug.WriteLine(Message);

                                                Message = System.String.Format
                                                    (
                                                    "EventManagerThreadProc(): Adding event with subscription '{0}' to stack of items to ignore.",
                                                    rawEventSubscriptionID
                                                    );
                                                System.Diagnostics.Debug.WriteLine(Message);
                                            }

                                            //  Add SubscriptionID to stack of items to ignore
                                            //  and continue to loop to the next raw event in the EventList
                                            s_SubscriptionIDIgnoreStack.Push(rawEventSubscriptionID);
                                            continue;
                                        }

                                        //  Save reference to event manager for later processing
                                        EventManagerHashtable[EventManagerKey] = EventManager;

                                        //  Process the event
                                        EventManager.ProcessRawEvent(RawEvent);
                                    }

                                    //  Iterate through event manager instances that processed events
                                    foreach (string EventManagerKey in EventManagerHashtable.Keys)
                                    {
                                        GrooveWebServicesV12Helpers.EventManager EventManager = (GrooveWebServicesV12Helpers.EventManager)s_EventManagerHashtable[EventManagerKey];
                                        if (EventManager == null)
                                            throw new System.Exception("EventManager instance not found(3)!");

                                        //  Indicate event processing completed
                                        EventManager.SignalEventsProcessingCompleted(ExtendedEventData.EventQueueSummary.Sequence);
                                    }

                                    //  If we get here, we can assume all events have been processed successfully, so we can
                                    //  delete the events up to the highest sequence;
                                    if (GrooveWebServicesV12Helpers.Context.TraceEnabled)
                                    {
                                        string Message = System.String.Format
                                            (
                                            "EventManagerThreadProc(): events processed. Deleting events up to sequence {0}.",
                                            ExtendedEventData.EventQueueSummary.Sequence
                                            );
                                        System.Diagnostics.Debug.WriteLine(Message);
                                    }

                                    // must pass Sequence - 1, because Sequence (with an empty queue) starts from 1
                                    // and increments from there. Without the -1, you may delete the "next" event ...rgn
                                    Event.Delete(ExtendedEventData.EventQueueSummary.Sequence - 1);

                                    //  Iterate through event manager instances that processed events
                                    foreach (string EventManagerKey in EventManagerHashtable.Keys)
                                    {
                                        GrooveWebServicesV12Helpers.EventManager EventManager = (GrooveWebServicesV12Helpers.EventManager)s_EventManagerHashtable[EventManagerKey];
                                        if (EventManager == null)
                                            throw new System.Exception("EventManager instance not found(4)!");

                                        //  Indicate events deleted from queue
                                        EventManager.SignalEventsDeleted(ExtendedEventData.EventQueueSummary.Sequence);
                                    }
                                }

                                //  Iterate through all event manager instances
                                foreach (string EventManagerKey in s_EventManagerHashtable.Keys)
                                {
                                    GrooveWebServicesV12Helpers.EventManager EventManager = (GrooveWebServicesV12Helpers.EventManager)s_EventManagerHashtable[EventManagerKey];
                                    if (EventManager == null)
                                        throw new System.Exception("EventManager instance not found(2)!");

                                    //  Check to see if lifetime in hours exceeds half of
                                    //  time to live - if so then we will update subscription now
                                    //  and reset counter
                                    if (EventManager.LifetimeInHours >= (EventManager.TimeToLive / 2.0))
                                    {
                                        if (GrooveWebServicesV12Helpers.Context.TraceEnabled)
                                            System.Diagnostics.Debug.WriteLine("Updating subscription '" + EventManager.Subscription.SubscriptionID + "'");

                                        EventManager.UpdateSubscription();
                                        EventManager.ResetSubscriptionLifetime();
                                    }
                                }
                            }
                        }

                        // Sleep between event polling;
                        System.Threading.Thread.Sleep(s_SleepDuration);
                    }
                    catch (System.Threading.ThreadAbortException)
                    {
                        return;
                    }
                    catch (System.Net.WebException webEx)
                    {
                        if (webEx.Status == System.Net.WebExceptionStatus.ConnectFailure)
                        {
                            // groove has gone away. what to do about it?
                            // one problem is that you've skipped the Sleep, 
                            // ... so you're cycling in this exception rapidly and continuously

                            // break out into a different, longer sleep, waiting to get a connection to Groove
                            break;
                        }
                        else
                        {
                            // ... hmmm. I dunno. might as well break.
                            System.Diagnostics.Debug.WriteLine("Error in EventManagerThreadProc: " + webEx.Message);
                            System.Diagnostics.Debug.WriteLine(webEx.StackTrace);
                            break;
                        }
                    }
                    catch (System.Web.Services.Protocols.SoapException soapEx)
                    {
                        //// no good way to get at the detail you want
                        //// System.Xml.XmlQualifiedName n = ((System.Web.Services.Protocols.SoapException)Exception).Code;

                        if (soapEx.Message == "Groove request key does not match")
                        {
                            // hmmm... the likely situation is that you're local and Groove has restarted
                            // if that's the case, fix it - get a new RequestKey/ResponseKey (and event...)
                            Array a = Array.CreateInstance(typeof(DictionaryEntry), s_EventHashtable.Count);
                            s_EventHashtable.CopyTo(a, 0);
                            s_EventHashtable.Clear();
                            foreach (DictionaryEntry de in a)
                            {
                                string key = (string)de.Key;
                                Event Event = (Event)de.Value;

                                // get a like context with a new RequestKey and local response key
                                Context ctx = Context.NewContext(Event.Context);
                                if (ctx != null)
                                {
                                    // you just want to set a new context object into the event...
                                    // but you need to force the Event to use it! so, cook a new event
                                    // that will force creating a new web service with a fresh header
                                    // (That's the rub - the header holds a reference to the actual local request key string
                                    //  and there's no way to "replace" it)
                                    Event e = new Event(ctx, Event.IdentityURL, Event.CallbackURI);
                                    s_EventHashtable.Add(key, e);
                                }
                                else
                                {
                                    // if ctx is null, then the old Context object wasn't local
                                    // and there isn't much you can do about getting a new RemoteKey
                                    // so put the old Event back in the table
                                    s_EventHashtable.Add(key, Event);
                                }
                            }
                        }
                        else
                        {
                            System.Diagnostics.Debug.WriteLine("Error in EventManagerThreadProc: " + soapEx.Message);
                            System.Diagnostics.Debug.WriteLine(soapEx.StackTrace);
                            break;
                        }
                    }
                    catch (System.Exception Exception)
                    {
                        System.Diagnostics.Debug.WriteLine("Error in EventManagerThreadProc: " + Exception.Message);
                        System.Diagnostics.Debug.WriteLine(Exception.StackTrace);
                        break;
                    }
                } while (true);

                // why are we here? 
                // hopefully because we've caught a ConnectFailure exception
                // ... delay before trying to connect again

                // wait 30 seconds for Groove to start, rather than bang away continuously...
                if (GrooveWebServicesV12Helpers.Context.TraceEnabled)
                {
                    System.Diagnostics.Debug.WriteLine("EventManagerThreadProc: wait 30 seconds for Groove to start");
                }
                System.Threading.Thread.Sleep(new TimeSpan(0, 0, 30));

            } while (true);
        }

		#endregion

	}
}
